package main

import (
	"errors"
	"fmt"
	"hash/crc32"
	"io"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"net/url"
	"os"
	"path"
	"strconv"
	"strings"

	LRUCache "github.com/hashicorp/golang-lru"
	requests "github.com/imroc/req"
)

var mp4cache *LRUCache.Cache

const cacheSize = 640 * 1024

type Range struct {
	w            http.ResponseWriter
	r            *http.Request
	basePath     string
	target       string
	cachesize    int64
	extension    string
	filerealname string
	currentidx   int
	totalcount   int
	totalbyte    int64
	crcname      string
	isProxy      bool
	fname        string
	childDir     string
	intEnd       int64
}

func RangeInit() {
	mp4cache, _ = LRUCache.New(200)
}
func NewRangeHandle(w http.ResponseWriter, r *http.Request, target string, isProxy bool, fname string) *Range {
	path, err := os.Getwd()
	if err != nil {
		log.Println(err)
		return nil
	}
	return &Range{
		w:         w,
		r:         r,
		basePath:  path,
		target:    target,
		isProxy:   isProxy,
		fname:     fname,
		cachesize: cacheSize,
	}
}
func (server *Range) Handle() {
	var start int64 = 0
	var (
		end      = ""
		filename = ""
	)
	u, err := url.Parse(server.target)
	server.PanicError(err)
	filename = u.Path
	ranges := server.r.Header.Get("Range")
	if ranges != "" {
		partstr := strings.ReplaceAll(ranges, "bytes=", "")
		parts := strings.Split(partstr, "-")
		partialstart := parts[0]
		partialend := parts[1]

		start, _ = strconv.ParseInt(partialstart, 10, 64)
		if partialend != "" {
			end = partialend
		}
	}
	server.reponse(start, end, filename)
}

func (server *Range) reponse(start int64, end string, filename string) {
	server.currentidx = int(start / server.cachesize)
	server.extension = path.Ext(filename)
	server.filerealname = strings.TrimSuffix(filename, server.extension)
	if server.isProxy { //传过来的name
		server.filerealname = server.fname
	}

	crcname := crc32.ChecksumIEEE([]byte(server.filerealname))

	server.crcname = fmt.Sprint(crcname)
	server.childDir = server.crcname[:2]
	childDirName := "cache/" + server.childDir
	isDir := Exists(childDirName)
	if !isDir { //创建子文件夹
		errMk := os.Mkdir(childDirName, os.ModePerm)
		if errMk != nil {
			log.Println(errMk)
		}
	}
	server.getTotalLength()

	total := server.totalbyte
	var intEnd int64
	intEnd, _ = strconv.ParseInt(end, 10, 64)

	if end != "" && intEnd < total {
		server.intEnd = intEnd
	} else {
		intEnd = total - 1
	}

	if intEnd == 1 {
		total = 2
	}
	conLen := strconv.FormatInt(total-int64(start), 10)

	//conLen := strconv.FormatInt(intEnd-int64(start)+1, 10)

	server.w.Header().Set("Cache-Control", "public, max-age=10368000")
	server.w.Header().Set("Accept-Ranges", "bytes")
	server.w.Header().Set("Content-Type", "video/mp4")
	server.w.Header().Set("Connection", "keep-alive")
	// server.w.Header().Set("Expires", "Tue, 24 Mar 2050 15:58:58 GMT")
	// server.w.Header().Set("Last-Modified", "Sat, 26 Oct 2010 13:08:37 GMT")
	server.w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, intEnd, server.totalbyte))
	// var _length int64 = 0
	// if (server.currentidx + 1) == server.totalcount {
	// 	_length = server.totalbyte - start
	// } else {
	// 	_length = int64(server.currentidx+1)*server.cachesize - start
	// }
	// server.w.Header().Set("Content-Length", strconv.FormatInt(_length, 10))
	server.w.Header().Set("Content-Length", conLen)
	server.w.WriteHeader(206)
	round := int64(start)
	// fmt.Println("fuck111 fuck111 fuck111", round, server.filerealname, server.crcname)

	// server.printNetwork(start, end, filename)
MainFor:
	for round < server.totalbyte {
		server.currentidx = int(round / server.cachesize)
		_, err := server.GetCurrentFile()
		if err != nil {
			// break
			// fmt.Println("from network:::::::", server.currentidx)
			_round, err := server.printNetwork(round, end, filename)
			round = _round
			if err != nil {
				fmt.Println("MainFor from network:::::error:::::", err)
				break MainFor
			}
		} else {
			//fmt.Println("from local:::::::", server.currentidx)
			_round, err := server.printLocal(round, end, filename)
			round = _round
			if err != nil {
				//fmt.Println("from local:::::error:::::", err)
				break MainFor
			}
		}
		// fmt.Println("round", round, "site", server.target)
	}
	// fmt.Println("fuck fuck fuck end :::::::::", round, server.filerealname, server.crcname)
}

func (server *Range) printNetwork(start int64, end string, filename string) (int64, error) {
	var total int64
	var err error
	var errNet error
	var block int
	var tempcachefile *os.File
	_start := start - (start % server.cachesize)
	_diff := int(start - _start)
	var _range string
	if (server.currentidx + 1) == server.totalcount {
		_range = fmt.Sprintf("bytes=%d-%d", _start, server.totalbyte-1)
	} else {
		_range = fmt.Sprintf("bytes=%d-%d", _start, _start+server.cachesize-1)
	}
	header := requests.Header{
		"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1",
		"Connection": "keep-alive",
		"Range":      _range,
	}
	_r, err := requests.Get(server.target, header)
	server.PanicError(err)
	resp := _r.Response()
	defer func() {
		resp.Body.Close()
	}()
	if resp.StatusCode <= 210 && resp.StatusCode >= 200 {
		tempfilepath := path.Join("./tmp", fmt.Sprintf("%s-%d.tmp", server.crcname, server.currentidx))
		tempcachefile, err = os.OpenFile(tempfilepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
		server.PanicError(err)
		defer func() {
			tempcachefile.Close()
		}()
		body := _r.Response().Body
		buf := make([]byte, 32*1024)
	NetFor:
		for {
			block, errNet = io.ReadAtLeast(body, buf, 32*1024)
			total += int64(block)
			if block == 0 {
				fmt.Println("unexp", err)
				break NetFor
			}
			// fmt.Println("printNetwork from network ::::::error debug :::::", _diff, block, err)
			if _diff >= 32*1024 {
				_, err = tempcachefile.Write(buf[:block])
				_diff = _diff - block
				if err != nil {
					break NetFor
				}
			} else if _diff > 0 && _diff < 32*1024 {
				_, err = tempcachefile.Write(buf[:block])
				if err != nil {
					break NetFor
				}
				_, err = server.w.Write(buf[_diff:block])
				_diff = 0
				if err != nil {
					break NetFor
				}
			} else {
				if server.intEnd != 0 && server.intEnd < int64(block) {
					block = int(server.intEnd)
				}
				_, err = tempcachefile.Write(buf[:block])
				if err != nil {
					break NetFor
				}
				_, err = server.w.Write(buf[:block])
				if err != nil {
					break NetFor
				}
			}

			if (total >= int64(server.cachesize)) || ((int64(server.cachesize)*int64(server.currentidx) + total) >= server.totalbyte) {
				server.closeCache(tempcachefile)
				// fmt.Println("finish ", server.filerealname, server.currentidx)
				break
			}
			if err != nil || errNet != nil {
				break NetFor
			}
		}
		//fmt.Println("total is :::::", total)
		return _start + total, err
	} else {
		err = errors.New("request error")
		return 0, err
	}
}

func (server *Range) printLocal(start int64, end string, filename string) (int64, error) {
	var total int64
	var err error
	var block int
	_start := start - (start % server.cachesize)
	_diff := int(start - _start)

	var len int64
	//if end != "" && server.intEnd != 0 {
	//	len = server.intEnd - start + 1
	//	if len <= 1 {
	//		_diff = 0
	//	}
	//}

	cachefilepath := path.Join("./cache", server.childDir, server.crcname+"-"+strconv.Itoa(server.currentidx))
	cachefile, _ := os.OpenFile(cachefilepath, os.O_RDONLY, os.ModePerm)
	defer cachefile.Close()
	byteSize := 32 * 1024
	buf := make([]byte, byteSize)
LocalFor:
	for {
		block, err = io.ReadAtLeast(cachefile, buf, byteSize)
		total += int64(block)
		if block == 0 || err != nil {
			if err != io.ErrUnexpectedEOF && err != io.EOF {
				fmt.Println(err)
				break LocalFor
			}
		}

		if _diff >= byteSize {
			_diff = _diff - block
		} else if _diff > 0 && _diff < byteSize {
			_, err1 := server.w.Write(buf[_diff:block])
			if err1 != nil {
				fmt.Println("err bt", err1)
				err = err1
				break LocalFor
			}
			_diff = 0
		} else {
			if server.intEnd != 0 && server.intEnd < int64(block) {
				block = int(server.intEnd)
			}

			//if server.intEnd != 0 && total > len {
			//	block = int(len) % byteSize
			//}

			_, err1 := server.w.Write(buf[:block])
			if err1 != nil {
				fmt.Println("err1", err1)
				err = err1
				break LocalFor
			}

			if (server.intEnd != 0 && total > len) || len == 1 {
				break LocalFor
			}
		}
		if err != nil {
			err = nil
			break
		}
	}
	round := _start + total
	//if server.intEnd != 0 {
	//	round = len - 1
	//}
	return round, err
}

func (server *Range) closeCache(tempcachefile *os.File) {
	if tempcachefile != nil {
		tempcachefile.Close()
		os.Rename(tempcachefile.Name(), path.Join("./cache", server.childDir, server.crcname+"-"+strconv.Itoa(server.currentidx)))
	}
}
func (server *Range) GetCurrentFile() (string, error) {
	filepath := path.Join("./cache", server.childDir, fmt.Sprintf("%s-%d", server.crcname, server.currentidx))
	// fmt.Println(filepath)
	_, err := os.Stat(filepath)
	if err != nil {
		return "", err
	} else {
		return filepath, nil
	}
}
func (server *Range) getTotalLength() {
	_, err := os.Stat(path.Join("./cache", server.childDir, server.crcname))
	if err != nil {
		header := requests.Header{
			"Accept":          "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
			"accept-encoding": "gzip, deflate, br",
			"accept-language": "en-US,en;q=0.9,zh;q=0.8,zh-CN;q=0.7",
			"cache-control":   "no-cache",
			"pragma":          "no-cache",
			"User-Agent":      "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1",
			// "Connection": "keep-alive",
			// "Range": "bytes=0-",
		}
		fmt.Println("server.target is :::::::", server.target)
		_r, err := requests.Get(server.target, header)
		server.PanicError(err)
		resp := _r.Response()
		defer func() {
			resp.Body.Close()
		}()
		if resp.StatusCode != 200 && resp.StatusCode != 206 {
			body, _ := ioutil.ReadAll(resp.Body)
			fmt.Println("==result is:==", string(body))
			server.PanicError(errors.New("url error"))
		}
		f, _ := os.OpenFile(path.Join("./cache", server.childDir, server.crcname), os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm)
		f.WriteString(resp.Header.Get("Content-Length"))
		f.Close()
	}
	cl, err := ioutil.ReadFile(path.Join("./cache", server.childDir, server.crcname))
	server.PanicError(err)
	contentlengthInt, err := strconv.ParseInt(string(cl), 10, 64)
	server.PanicError(err)
	server.totalbyte = contentlengthInt
	server.totalcount = int(math.Ceil(float64(server.totalbyte) / float64(server.cachesize)))
}
func (this *Range) PanicError(err error) {
	if err != nil {
		panic(err)
	}
}
func (this *Range) PrintError(err error) {
	if err != nil {
		fmt.Println(err)
	}
}
